// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/compactbinary.h>
#include <zencore/endian.h>
#include <zencore/iobuffer.h>
#include <zencore/iohash.h>
#include <zencore/memory.h>

namespace zen {

namespace CompactBinaryPrivate {

	template<typename T>
	static constexpr inline T ReadUnaligned(const void* const Memory)
	{
#if ZEN_PLATFORM_SUPPORTS_UNALIGNED_LOADS
		return *static_cast<const T*>(Memory);
#else
		T Value;
		memcpy(&Value, Memory, sizeof(Value));
		return Value;
#endif
	}
}  // namespace CompactBinaryPrivate
/**
 * A type that provides unchecked access to compact binary values.
 *
 * The main purpose of the type is to efficiently switch on field type. For every other use case,
 * prefer to use the field, array, and object types directly. The accessors here do not check the
 * type before reading the value, which means they can read out of bounds even on a valid compact
 * binary value if the wrong accessor is used.
 */
class CbValue
{
public:
	CbValue(CbFieldType Type, const void* Value);

	CbObjectView AsObjectView() const;
	CbArrayView	 AsArrayView() const;

	MemoryView AsBinary() const;

	/** Access as a string. Checks for range errors and uses the default if OutError is not null. */
	std::string_view AsString(CbFieldError* OutError = nullptr, std::string_view Default = std::string_view()) const;

	/** Access as a string as UTF8. Checks for range errors and uses the default if OutError is not null. */
	std::u8string_view AsU8String(CbFieldError* OutError = nullptr, std::u8string_view Default = std::u8string_view()) const;

	/**
	 * Access as an integer, with both positive and negative values returned as unsigned.
	 *
	 * Checks for range errors and uses the default if OutError is not null.
	 */
	uint64_t AsInteger(CompactBinaryPrivate::IntegerParams Params, CbFieldError* OutError = nullptr, uint64_t Default = 0) const;

	uint64_t AsIntegerPositive() const;
	int64_t	 AsIntegerNegative() const;

	float  AsFloat32() const;
	double AsFloat64() const;

	bool AsBool() const;

	inline IoHash AsObjectAttachment() const { return AsHash(); }
	inline IoHash AsBinaryAttachment() const { return AsHash(); }
	inline IoHash AsAttachment() const { return AsHash(); }

	IoHash AsHash() const;
	Guid   AsUuid() const;

	int64_t AsDateTimeTicks() const;
	int64_t AsTimeSpanTicks() const;

	Oid AsObjectId() const;

	CbCustomById   AsCustomById() const;
	CbCustomByName AsCustomByName() const;

	inline CbFieldType GetType() const { return Type; }
	inline const void* GetData() const { return Data; }

private:
	const void* Data;
	CbFieldType Type;
};

inline CbFieldView::CbFieldView(const CbValue& InValue) : Type(InValue.GetType()), Payload(InValue.GetData())
{
}

inline CbValue
CbFieldView::GetValue() const
{
	return CbValue(CbFieldTypeOps::GetType(Type), Payload);
}

inline CbValue::CbValue(CbFieldType InType, const void* InValue) : Data(InValue), Type(InType)
{
}

inline CbObjectView
CbValue::AsObjectView() const
{
	return CbObjectView(*this);
}

inline CbArrayView
CbValue::AsArrayView() const
{
	return CbArrayView(*this);
}

inline MemoryView
CbValue::AsBinary() const
{
	const uint8_t* const Bytes = static_cast<const uint8_t*>(Data);
	uint32_t			 ValueSizeByteCount;
	const uint64_t		 ValueSize = ReadVarUInt(Bytes, ValueSizeByteCount);
	return MakeMemoryView(Bytes + ValueSizeByteCount, ValueSize);
}

inline std::string_view
CbValue::AsString(CbFieldError* OutError, std::string_view Default) const
{
	const char* const Chars = static_cast<const char*>(Data);
	uint32_t		  ValueSizeByteCount;
	const uint64_t	  ValueSize = ReadVarUInt(Chars, ValueSizeByteCount);

	if (OutError)
	{
		if (ValueSize >= (uint64_t(1) << 31))
		{
			*OutError = CbFieldError::RangeError;
			return Default;
		}
		*OutError = CbFieldError::None;
	}

	return std::string_view(Chars + ValueSizeByteCount, int32_t(ValueSize));
}

inline std::u8string_view
CbValue::AsU8String(CbFieldError* OutError, std::u8string_view Default) const
{
	const char8_t* const Chars = static_cast<const char8_t*>(Data);
	uint32_t			 ValueSizeByteCount;
	const uint64_t		 ValueSize = ReadVarUInt(Chars, ValueSizeByteCount);

	if (OutError)
	{
		if (ValueSize >= (uint64_t(1) << 31))
		{
			*OutError = CbFieldError::RangeError;
			return Default;
		}
		*OutError = CbFieldError::None;
	}

	return std::u8string_view(Chars + ValueSizeByteCount, int32_t(ValueSize));
}

inline uint64_t
CbValue::AsInteger(CompactBinaryPrivate::IntegerParams Params, CbFieldError* OutError, uint64_t Default) const
{
	// A shift of a 64-bit value by 64 is undefined so shift by one less because magnitude is never zero.
	const uint64_t OutOfRangeMask = uint64_t(-2) << (Params.MagnitudeBits - 1);
	const uint64_t IsNegative	  = uint8_t(Type) & 1;

	uint32_t	   MagnitudeByteCount;
	const uint64_t Magnitude = ReadVarUInt(Data, MagnitudeByteCount);
	const uint64_t Value	 = Magnitude ^ -int64_t(IsNegative);

	if (OutError)
	{
		const uint64_t IsInRange = (!(Magnitude & OutOfRangeMask)) & ((!IsNegative) | Params.IsSigned);
		*OutError				 = IsInRange ? CbFieldError::None : CbFieldError::RangeError;

		const uint64_t UseValueMask = -int64_t(IsInRange);
		return (Value & UseValueMask) | (Default & ~UseValueMask);
	}

	return Value;
}

inline uint64_t
CbValue::AsIntegerPositive() const
{
	uint32_t MagnitudeByteCount;
	return ReadVarUInt(Data, MagnitudeByteCount);
}

inline int64_t
CbValue::AsIntegerNegative() const
{
	uint32_t MagnitudeByteCount;
	return int64_t(ReadVarUInt(Data, MagnitudeByteCount)) ^ -int64_t(1);
}

inline float
CbValue::AsFloat32() const
{
	const uint32_t Value = FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<uint32_t>(Data));
	return reinterpret_cast<const float&>(Value);
}

inline double
CbValue::AsFloat64() const
{
	const uint64_t Value = FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<uint64_t>(Data));
	return reinterpret_cast<const double&>(Value);
}

inline bool
CbValue::AsBool() const
{
	return uint8_t(Type) & 1;
}

inline IoHash
CbValue::AsHash() const
{
	return IoHash::MakeFrom(Data);
}

inline Guid
CbValue::AsUuid() const
{
	Guid Value;
	memcpy(&Value, Data, sizeof(Guid));
	Value.A = FromNetworkOrder(Value.A);
	Value.B = FromNetworkOrder(Value.B);
	Value.C = FromNetworkOrder(Value.C);
	Value.D = FromNetworkOrder(Value.D);
	return Value;
}

inline int64_t
CbValue::AsDateTimeTicks() const
{
	return FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<int64_t>(Data));
}

inline int64_t
CbValue::AsTimeSpanTicks() const
{
	return FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<int64_t>(Data));
}

inline Oid
CbValue::AsObjectId() const
{
	return Oid::FromMemory(Data);
}

inline CbCustomById
CbValue::AsCustomById() const
{
	const uint8_t* Bytes = static_cast<const uint8_t*>(Data);
	uint32_t	   DataSizeByteCount;
	const uint64_t DataSize = ReadVarUInt(Bytes, DataSizeByteCount);
	Bytes += DataSizeByteCount;

	CbCustomById Value;
	uint32_t	 TypeIdByteCount;
	Value.Id   = ReadVarUInt(Bytes, TypeIdByteCount);
	Value.Data = MakeMemoryView(Bytes + TypeIdByteCount, DataSize - TypeIdByteCount);
	return Value;
}

inline CbCustomByName
CbValue::AsCustomByName() const
{
	const uint8_t* Bytes = static_cast<const uint8_t*>(Data);
	uint32_t	   DataSizeByteCount;
	const uint64_t DataSize = ReadVarUInt(Bytes, DataSizeByteCount);
	Bytes += DataSizeByteCount;

	uint32_t	   TypeNameLenByteCount;
	const uint64_t TypeNameLen = ReadVarUInt(Bytes, TypeNameLenByteCount);
	Bytes += TypeNameLenByteCount;

	CbCustomByName Value;
	Value.Name = std::u8string_view(reinterpret_cast<const char8_t*>(Bytes), static_cast<std::u8string_view::size_type>(TypeNameLen));
	Value.Data = MakeMemoryView(Bytes + TypeNameLen, DataSize - TypeNameLen - TypeNameLenByteCount);
	return Value;
}

}  // namespace zen
